我們仔細看昨天的流程會發現,要調用Google Drive之前一定要先拿使用者的token去建立*http.Client
然後再交給drive.NewService
去作操作,調用其他Google的API也是相似的作法,也就是要先有GoogleOAuth
才有GoogleDrive
,所以我們先來微調一下昨天的程式。
// oauth.go
package google
import (
"context"
"log"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/drive/v3"
"google.golang.org/api/option"
)
type GoogleOAuth struct {
Config *oauth2.Config
}
func NewGoogleOAuth(clientID string, clientSecret string, redirectURL string) *GoogleOAuth {
return &GoogleOAuth{
Config: &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: google.Endpoint,
Scopes: []string{drive.DriveScope},
RedirectURL: redirectURL,
},
}
}
func (oa *GoogleOAuth) OAuthLoginURL() (oauthURL string) {
oauthURL = oa.Config.AuthCodeURL("state-token", oauth2.AccessTypeOffline)
return oauthURL
}
func (oa *GoogleOAuth) UserOAuthToken(authCode string) (*oauth2.Token, error) {
token, err := oa.Config.Exchange(context.TODO(), authCode)
if err != nil {
log.Printf("Unable to retrieve token from web %v", err)
return nil, err
}
return token, nil
}
func (oa *GoogleOAuth) NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*GoogleDrive, error) {
client := oa.Config.Client(ctx, tok)
srv, err := drive.NewService(ctx, option.WithHTTPClient(client))
if err != nil {
log.Printf("Unable to retrieve Drive client: %v", err)
return nil, err
}
return &GoogleDrive{
Service: srv,
}, nil
}
我們把NewGoogleDrive
拉到oauth.go,並且變成GoogleOAuth
的方法,然後把原本NewClientByUserToken
放進去NewGoogleDrive
裡面,畢竟NewClient
現在的目的就是去NewGoogleDrive
,我們讓GoogleOAuth
變成對外的統一入口,來操作各種GoogleAPI。如果以後有其他GoogleAPI服務要用,就在這邊繼續NewGoogleXXX
就好了。而drive.go就保持存放專門操作google drive的部分~
接著我們在/adapter/google
下建立interface.go,讓GoogleOAuth的方法對外
package google
import (
"context"
"golang.org/x/oauth2"
)
type GoogleOAuthI interface {
OAuthLoginURL() (oauthURL string)
UserOAuthToken(authCode string) (*oauth2.Token, error)
NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*GoogleDrive, error)
}
下一步,我們來去實現Service層
首先,像Sample Service一樣,我們建立以下對應的檔案
建立interface.go並宣告DriveServiceGoogleOAuthI
,我們複製GoogleOAuthI的方法,讓GoogleOAuth能被注入近來這個Service。
// interface.go
package drive
import (
"context"
"github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/google"
"golang.org/x/oauth2"
)
type DriveServiceGoogleOAuthI interface {
OAuthLoginURL() (oauthURL string)
UserOAuthToken(authCode string) (*oauth2.Token, error)
NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*google.GoogleDrive, error)
}
建立service.go,讓New一個GoogleDriveService的時候,要放入有實現DriveServiceGoogleOAuthI
的實例。
// service.go
package drive
import "context"
type GoogleDriveService struct {
driveServiceGoogleOA DriveServiceGoogleOAuthI
}
type GoogleDriveServiceParam struct {
DriveServiceGoogleOA DriveServiceGoogleOAuthI
}
func NewGoogleDriveService(_ context.Context, param GoogleDriveServiceParam) *GoogleDriveService {
return &GoogleDriveService{
driveServiceGoogleOA: param.DriveServiceGoogleOA,
}
}
建立drive_service.go,先寫一個ListFiles()
,這部分之後還會改,這邊先確定可以正確調用GoogleOAuth
和GoogleDrive
的方法。
// drive_service.go
package drive
import (
"context"
"log"
)
func (dr *GoogleDriveService) ListFiles(authCode string, ctx context.Context) (map[string]string, error) {
tok, err := dr.driveServiceGoogleOA.UserOAuthToken(authCode)
if err != nil {
log.Panicln(err)
return nil, err
}
d, err := dr.driveServiceGoogleOA.NewGoogleDrive(ctx, tok)
if err != nil {
log.Panicln(err)
return nil, err
}
result, err := d.ListFiles(10)
if err != nil {
log.Panicln(err)
return nil, err
}
return result, nil
}
接著我們到application.go,把Drive Service註冊進Application中,並且提供外部NewApplication
的時候,放入實現google.GoogleOAuthI
的oauth ,然後注入到GoogleDriveService中。
// application.go
package app
import (
"context"
"github.com/line/line-bot-sdk-go/v7/linebot"
"github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/dynamodb"
"github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/google"
serviceDrive "github.com/onepiece010938/Line2GoogleDriveBot/internal/app/service/drive"
serviceSample "github.com/onepiece010938/Line2GoogleDriveBot/internal/app/service/sample"
)
type Application struct {
SampleService *serviceSample.SampleService
DriveService *serviceDrive.GoogleDriveService
LineBotClient *linebot.Client
}
func NewApplication(ctx context.Context, dynamodb dynamodb.DynamodbI, oauth google.GoogleOAuthI, lineBotClient *linebot.Client) *Application {
app := &Application{
LineBotClient: lineBotClient,
SampleService: serviceSample.NewSampleService(ctx, serviceSample.SampleServiceParam{
SampleServiceDynamodb: dynamodb,
}),
DriveService: serviceDrive.NewGoogleDriveService(ctx, serviceDrive.GoogleDriveServiceParam{
DriveServiceGoogleOA: oauth,
}),
}
return app
}
最後我們到server.go,在StartNgrokServer()
和NewGinLambda()
做NewApplication
的地方補上NewGoogleOAuth
並注入到Application中
// StartNgrokServer()
oauth := google.NewGoogleOAuth(os.Getenv("ClientID"), os.Getenv("ClientSecret"), os.Getenv("RedirectURL"))
app := app.NewApplication(rootCtx, db, oauth, lineClient)
// NewGinLambda()
googleClientID, err := ssmsvc.FindParameter(rootCtx, ssmsvc.Client, "GOOGLE_CLIENT_ID")
if err != nil {
log.Println(err)
}
googleClientSecret, err := ssmsvc.FindParameter(rootCtx, ssmsvc.Client, "GOOGLE_CLIENT_SECRET")
if err != nil {
log.Println(err)
}
redirectURL, err := ssmsvc.FindParameter(rootCtx, ssmsvc.Client, "REDIRECT_URL")
if err != nil {
log.Println(err)
}
oauth := google.NewGoogleOAuth(googleClientID, googleClientSecret, redirectURL)
app := app.NewApplication(rootCtx, db, oauth, lineClientLambda)
那我們今天就先到這兒,明天見囉~